#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

typedef struct Var_t              /*  Variables sent to threads   */
{
    pthread_cond_t  finished1   ;
    pthread_cond_t  finished2   ;
    pthread_mutex_t mutex1      ;
    pthread_mutex_t mutex2      ;
}Var;


void* PingThread(void* _var)
{
    assert(_var);

    while(1)
    {
        printf("ping\t");
        usleep(10000);

        pthread_mutex_lock((pthread_mutex_t*)(&((Var*)_var)->mutex2));
        pthread_cond_signal(&((Var*)_var)->finished2);
        pthread_mutex_unlock((pthread_mutex_t*)(&((Var*)_var)->mutex2));     /*  unlock mutex        */


        pthread_mutex_lock((pthread_mutex_t*)(&((Var*)_var)->mutex1));
        pthread_cond_wait(&((Var*)_var)->finished1, &((Var*)_var)->mutex1);
        pthread_mutex_unlock((pthread_mutex_t*)(&((Var*)_var)->mutex1));     /*  unlock mutex        */
    }

    pthread_exit(0);

    return 0;
}


void* PongThread(void* _var)
{
    assert(_var);

    while(1)
    {
        pthread_mutex_lock((pthread_mutex_t*)(&((Var*)_var)->mutex2));
        pthread_cond_wait(&((Var*)_var)->finished2, &((Var*)_var)->mutex2);
        pthread_mutex_unlock((pthread_mutex_t*)(&((Var*)_var)->mutex2));     /*  unlock mutex        */

        printf("pong\n");
        usleep(10000);

        pthread_mutex_lock((pthread_mutex_t*)(&((Var*)_var)->mutex1));
        pthread_cond_signal(&((Var*)_var)->finished1);
        pthread_mutex_unlock((pthread_mutex_t*)(&((Var*)_var)->mutex1));     /*  unlock mutex        */
    }

    pthread_exit(0);

    return 0;
}

int main()
{
    pthread_t               tid[2]  ;                   /*  the ID's of threads */
    pthread_mutexattr_t     mutAtr1 ;
    pthread_mutexattr_t     mutAtr2 ;
    Var                     v       ;


    pthread_mutexattr_init(&mutAtr1);                                   /*  init attributes of mutex                                */
    pthread_mutexattr_setpshared(&mutAtr1, PTHREAD_PROCESS_PRIVATE);    /*  Set to threads shared                                   */
    pthread_mutexattr_settype(&mutAtr1, PTHREAD_MUTEX_RECURSIVE);       /*  Only process that locked can unlock. Mutex Re-entrant   */
    if(pthread_mutex_init(&(v.mutex1), &mutAtr1))                          /*  Init the mutex with the above attributes                */
    {
        fprintf(stderr, "error in initializing mutex\n");
        return 1;
    }

    pthread_mutexattr_init(&mutAtr2);                                   /*  init attributes of mutex                                */
    pthread_mutexattr_setpshared(&mutAtr2, PTHREAD_PROCESS_PRIVATE);    /*  Set to threads shared                                   */
    pthread_mutexattr_settype(&mutAtr2, PTHREAD_MUTEX_RECURSIVE);       /*  Only process that locked can unlock. Mutex Re-entrant   */
    if(pthread_mutex_init(&(v.mutex2), &mutAtr2))                          /*  Init the mutex with the above attributes                */
    {
        fprintf(stderr, "error in initializing mutex\n");
        return 1;
    }



    pthread_cond_init (&(v.finished1), NULL);
    pthread_cond_init (&(v.finished2), NULL);

    if(pthread_create(&tid[0],      /*  store the ID of the created thread  */
                      0,            /*  thread attributes                   */
                      PingThread ,  /*  start routine                       */
                      (void*)(&v))) /*  arguments for start routine         */
    {
       fprintf(stderr, "error in creating thread\n");
       return 1;
    }

    if(pthread_create(&tid[1], 0, PongThread , (void*)(&v)))
    {
       fprintf(stderr, "error in creating thread\n");
       return 1;
    }

    pthread_join(tid[0], 0);
    pthread_join(tid[1], 0);

    pthread_mutex_destroy(&v.mutex1);
    pthread_cond_destroy(&v.finished1);
    pthread_mutex_destroy(&v.mutex2);
    pthread_cond_destroy(&v.finished2);
    pthread_exit(0);

    return 0;
}
